package net.m510.autosurfer.core;

import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.m510.autosurfer.core.exception.UserProcessException;
import net.m510.autosurfer.core.exception.UserProcessRuntimeException;

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.jsoup.nodes.Document;

/**
 * 
 * @author mgoto
 *
 */
public abstract class AbstractMain {
	private Logger log = Logger.getLogger(AbstractMain.class.getName());
	private InternalState state = InternalState.NORMAL;
	protected CloseableHttpClient client;
	protected final HttpClientContext context = HttpClientContext.create();
	protected final BasicCookieStore cookies = new BasicCookieStore();

	protected abstract Document start() throws IOException, UserProcessException ;
	protected abstract Document login(Document doc) throws IOException, UserProcessException;
	protected abstract Document prepareDownload(Document doc) throws IOException, UserProcessException;
	protected abstract File download(Document doc) throws IOException, UserProcessException;
	protected abstract void logout(Document doc) throws IOException, UserProcessException;
	
	/**
	 * メインルーチン
	 * @param args
	 */
	public void Main(String[] args) {
		try {
			parseArgs(args);
		} catch (Exception e) {
			System.exit(InternalState.USR_ERR.getValue());
		}
			
		log.info("開始");
		try {
			log.info("準備処理");
			init();
			ready();
			log.info("主処理");
			Document topDoc = goTop();
			Document loginDoc = doLogin(topDoc);
			;
			try {
				Document dlDoc = goDownload(loginDoc);
				doDownload(dlDoc);
			} finally {
				doLogout(loginDoc);
			}
		} catch (UserProcessException | UserProcessRuntimeException upe) {
			handleUserProcessException(upe);
		} catch (Exception e) {
			log.log(Level.SEVERE,"システムエラーが発生しました。中断します",e);
			state = InternalState.SYS_ERR;
		} finally {
			log.info("終了処理");
			try {
				finish();
				client.close();
			} catch (Exception e) {
				handleFinishException(e);
			}
		}

		log.info("終了("+state+")");
		System.exit(state.getValue());
	}
	
	private Document goTop() throws Exception {
		log.info("トップページ処理開始");
		Document d = start();
		log.info("トップページ処理終了");
		return d;
	}
	private Document doLogin(Document topDoc) throws Exception {
		log.info("ログイン処理開始");
		Document d = login(topDoc);
		log.info("ログイン処理終了");
		return d;
	}
	private Document goDownload(Document loginDoc) throws Exception {
		log.info("ダウンロードページへの遷移開始");
		Document d = prepareDownload(loginDoc);
		log.info("ダウンロードページへの遷移終了");
		return d;
	}
	private File doDownload(Document dlDoc) throws Exception {
		log.info("ダウンロード処理開始");
		File f = download(dlDoc);
		log.info("ファイル "+f+"　に保存しました");
		log.info("ダウンロード処理終了");
		return f;
	}
	private void doLogout(Document loginDoc) throws Exception {
		try {
			log.info("ログアウト処理開始");
			logout(loginDoc);
			log.info("ログアウト処理終了");
		} catch (Exception e) {
			log.log(Level.WARNING,"ログアウト処理でエラーが発生しました。",e);
			if ( state == InternalState.NORMAL ) state = InternalState.WARNING;
		}
	}
	
	private void init() throws Exception {
		HttpClientBuilder builder = HttpClients.custom();
		builder.setRedirectStrategy(new LaxRedirectStrategy());
		builder.setDefaultCookieStore(cookies);
		customClientBuilder(builder);
		this.client = builder.build();
	}
	
	protected void customClientBuilder(HttpClientBuilder builder) {
		return;
	}
	
	protected void handleUserProcessException(Exception upe) {
		log.log(Level.SEVERE,"エラーが発生しました。"+upe.getClass().getName()+":"+upe.getMessage(),upe);
		log.log(Level.SEVERE,"中断します");
		state = InternalState.USR_ERR;
	}
	
	/**
	 * 準備処理
	 * @throws Exception
	 */
	protected void ready() throws Exception {
	}

	/**
	 * 終了処理
	 */
	protected void finish() throws Exception {
	}
	
	private void handleFinishException(Exception e) {
		if ( state == InternalState.NORMAL ) {
			log.log(Level.WARNING,"終了処理でエラーが発生しました。警告終了します",e);
			state = InternalState.WARNING;
		} else {
			log.log(Level.WARNING,"終了処理でエラーが発生しました。",e);
			if ( e instanceof UserProcessException || e instanceof UserProcessRuntimeException ) {
				state = InternalState.USR_ERR;
			} else {
				state = InternalState.SYS_ERR;
			}
		}
	}
	
	private void parseArgs(String[] args) {
		Options options = getOptions();
		BasicParser parser = new BasicParser();
		try {
			CommandLine cmd = parser.parse(options, args);
			setParams(cmd);
		} catch (ParseException e) {
			System.out.println(e.getMessage());
			HelpFormatter hf = new HelpFormatter();
			hf.setWidth(500);
			hf.printHelp("java "+this.getClass().getName(), options,true);
			throw new IllegalArgumentException(e);
		}
	}

	/**
	 * コマンドラインオプションを取り出す
	 * 
	 * @return コマンドラインオプション
	 */
	abstract protected Options getOptions();
	/**
	 * 解析済みのコマンドラインから、実行パラメータをセットする
	 * 
	 * @param cmd 解析済みのコマンドライン
	 */
	abstract protected void setParams(CommandLine cmd);

	public Logger getLogger() {
		return log;
	}

	public void setLogger(Logger logger) {
		this.log = logger;
	}
	protected void setState(InternalState state) {
		this.state = state;
	}

}
